////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////

// Header
#include "FTGAPlugin.hpp"

// Macros
#include "FTGAMacros.hpp"
#include "TKMacros.h"

// Random number generator
#include <cstdlib>

// String manipulation
#include <string>

// I/O stream
#include <iostream>

// Conversion functions
#include "Convert.hpp"

// Math
#include <cmath>

#ifdef DATA_COLLECTION
	// Vector and Triple
	#include <vector>
	#include "Triple.hpp"
	// Time
	#include <ctime>
	// Exception handling
	#include <exception>
	#include "IOFileException.hpp"
	// File stream
	#include <fstream>
#endif

// Namespaces
using namespace std;
using namespace Toolkit;
using namespace SC;

#ifdef DATA_COLLECTION
	using namespace Types;
	using namespace Exceptions;
#endif

////////////////////////////////////////////////////////////////////////////////
// Setup macros
////////////////////////////////////////////////////////////////////////////////

// Get the type of a system
#define Type(S) (S).GetKernel().substr(TYPE_OFFSET, TYPE_LENGTH)

////////////////////////////////////////////////////////////////////////////////
// Experiment data collection
////////////////////////////////////////////////////////////////////////////////

#ifdef DATA_COLLECTION
	static string program_name;
	static unsigned long nb_iterations, nb_computations;
	static vector< Triple<unsigned long, unsigned long, long double> > data;
#endif

////////////////////////////////////////////////////////////////////////////////
// Plugin init and exit
////////////////////////////////////////////////////////////////////////////////

void InitPlugin (const string & prog_name) {
	cout.precision(20);
	cerr.precision(20);
#ifdef DATA_COLLECTION
	program_name = prog_name;
	nb_iterations = 0;
	nb_computations = 0;
	data.clear();
#endif
}

void ClosePlugin () {
#ifdef DATA_COLLECTION
	// Save data to file
	ofstream out_data;
	char buffer[32];
	time_t tt = time(NULL);
	strftime(buffer,32,"%y%m%d_%H%M%S",localtime(&tt));
	out_data.open((program_name + ".dat").c_str(),ios_base::out|ios_base::app);
	// Test the file si open
	if (!out_data.is_open()) throw IOFileException("Cannot write to " + program_name + ".dat");
	// Write to file
	try {
		out_data.precision(20);
		out_data << program_name + "_" + string(buffer) << " = [" << endl;
		for (unsigned int i=0; i<data.size(); ++i)
			out_data << data[i].GetFirst() << " " << data[i].GetSecond() << " " << data[i].GetThird() << endl;
		if (data.size() > 0)
			out_data << nb_iterations << " " << data[data.size()-1].GetSecond() << " " << data[data.size()-1].GetThird() << endl;
		out_data << "];" << endl << endl;
	}
	catch (exception & e) {
		cerr << e.what() << endl;
	}
	// Close file
	if (out_data.is_open()) out_data.close();
#endif
}

////////////////////////////////////////////////////////////////////////////////
// SC Callbacks
////////////////////////////////////////////////////////////////////////////////

// Initialise and distribute a path holder
bool Initialise (SCSystem & solution, const SCSystem & initialiser, SCSystem & space) {
#ifdef DATA_COLLECTION
	// Update the number of iterations
	++nb_iterations;
#endif
	bool init = false;
	// See if solution is initialised
	unsigned int sol_lgh = BStrToUInt(solution.GetSchema1(SO_S1_SOL_LGH_IDX, INTEGER_SIZE));
	if (sol_lgh == 0) {
		// Get solution length
		unsigned int length = BStrToUInt(initialiser.GetKernel(IN_K_SOL_LGH_IDX, INTEGER_SIZE));
		// Store it into the solution
		solution.ReplaceSchema1(UIntToBStr(length,INTEGER_SIZE), SO_S1_SOL_LGH_IDX, INTEGER_SIZE);
		// Create random solution and store it
		string sol;
		for (unsigned int i=0; i<length; ++i)
			sol += (rand()&1)?"1":"0";
		solution.ReplaceSchema2(sol,SO_S2_SOL_IDX,length);
		init = true;
	}
	// Inject system into the computation space if possible
	if (space.Grab(solution))
		init = true;
#ifdef DATA_COLLECTION
	// Update the number of computations
	if (init)
		++nb_computations;
#endif
	// Computation happened
	return init;
}

const long double fitness(const string & sol) {
	long double fit = 0.;
	for (unsigned int i=0; i<sol.size(); ++i)
		fit += (sol[sol.size()-1-i]=='1')?1.:0.;
	return fit;
	/*
	long double fit = 0.;
	for (unsigned int i=0; i<sol.size(); ++i)
		fit += (sol[sol.size()-1-i]=='1')?pow((long double)2.,(long double)i):0.;
	return fit;
	*/
	/*
	#define SUMTARG 36.
	#define PRODTARG 360.
	long double sum = 0., prod = 1.;
	for (unsigned int i=0; i<sol.size(); ++i) {
		if (sol[i]=='1') sum += (long double)(i+1);
		else prod *= (long double)(i+1);
	}
	return 1. - ((abs(sum - SUMTARG)/SUMTARG) + (abs(prod - PRODTARG)/PRODTARG))/2.;
	*/
}

const string crossover(const string & sol1, const string & sol2) {
	string result, other;
	if (rand() & 1) {
		result = sol1;
		other = sol2;
	}
	else {
		result = sol2;
		other = sol1;
	}
	unsigned int pt1 = rand() % sol1.size(),
				 pt2 = rand() % sol1.size();
#ifdef PLUGIN_DEBUG
		cout << "       Crossover from " << pt1 << " to " << pt2 << ": " << result << endl;
#endif
	for (unsigned int i=pt1; i!=pt2; i = (i+1)%sol1.size())
		result[i] = other[i];
	return result;
}

const string mutate(const string & sol, double mutation_rate) {
	string result = sol;
#ifdef PLUGIN_DEBUG
	bool mutated = false;
#endif
	for (unsigned int i=0; i<result.size(); ++i) {
		if (frand() < mutation_rate) {
			result[i] = (result[i]=='1')?'0':((result[i]=='0')?'1':((rand()&1)?'1':'0'));
#ifdef PLUGIN_DEBUG
			mutated = true;
#endif
		}
	}
#ifdef PLUGIN_DEBUG
	if (mutated)
		cout << "       Mutation: " << sol << " -> " << result << endl;
#endif
	return result;
}

bool Operator (SCSystem & solution1, const SCSystem & op, SCSystem & solution2) {
#ifdef DATA_COLLECTION
	// Update the number of iterations
	++nb_iterations;
#endif
	// Get solutions
	unsigned int lgh1 = BStrToUInt(solution1.GetSchema1(SO_S1_SOL_LGH_IDX, INTEGER_SIZE)),
				 lgh2 = BStrToUInt(solution2.GetSchema1(SO_S1_SOL_LGH_IDX, INTEGER_SIZE));
	if (lgh1 != lgh2) {
#ifdef PLUGIN_DEBUG
		cout << "GA Op: Lengths are different: " << lgh1 << " " << lgh2 << endl;
#endif
		return false;
	}
	string	s1 = solution1.GetSchema2(SO_S2_SOL_IDX,lgh1),
			s2 = solution2.GetSchema2(SO_S2_SOL_IDX,lgh1);
	long double	fit1 = fitness(s1),
				fit2 = fitness(s2);
#ifdef PLUGIN_DEBUG
		cout << "GA Op: Fitnesses: " << fit1 << " " << fit2 << endl;
#endif
	// Select and evolve
	if (fit1 > fit2) {
		s2 = (Type(op) == OP_CO)?crossover(s1,s2):mutate(s1, ((double)BStrToUInt(op.GetKernel(OM_K_M_RATE_IDX, INTEGER_SIZE)))/10000.);
		solution2.ReplaceSchema2(s2,SO_S2_SOL_IDX,lgh1);
		fit2 = fitness(s2);
#ifdef PLUGIN_DEBUG
		cout << "       Replace S2: new fitness " << fit2 << endl;
#endif
	}
	else {
		s1 = (Type(op) == OP_CO)?crossover(s1,s2):mutate(s2, ((double)BStrToUInt(op.GetKernel(OM_K_M_RATE_IDX, INTEGER_SIZE)))/10000.);
		solution1.ReplaceSchema2(s1,SO_S2_SOL_IDX,lgh1);
		fit1 = fitness(s1);
#ifdef PLUGIN_DEBUG
		cout << "       Replace S1: new fitness " << fit1 << endl;
#endif
	}
#ifdef DATA_COLLECTION
	// Update the number of computations
	++nb_computations;
#endif
	// Computation happened
	return true;
}

bool SolTransfer (SCSystem & solution, const SCSystem & transfer, SCSystem & universe) {
#ifdef DATA_COLLECTION
	// Update the number of iterations
	++nb_iterations;
#endif
	// Get given solution
	unsigned int lgh = BStrToUInt(solution.GetSchema1(SO_S1_SOL_LGH_IDX, INTEGER_SIZE));
	string	sol = solution.GetSchema2(SO_S2_SOL_IDX,lgh);
	long double	fit = fitness(sol);
	// Read universe solution
	unsigned int best_lgh = BStrToUInt(universe.GetSchema1(UN_S1_SOL_LGH_IDX, INTEGER_SIZE));
	string	best_sol = (best_lgh!=0)?universe.GetSchema2(UN_S2_SOL_IDX,best_lgh):"";
	long double	best_fit = fitness(best_sol);
	// Compare
	if (fit > best_fit) {
		universe.ReplaceSchema1(solution.GetSchema1(SO_S1_SOL_LGH_IDX, INTEGER_SIZE), UN_S1_SOL_LGH_IDX, INTEGER_SIZE);
		universe.ReplaceSchema2(sol, UN_S2_SOL_IDX, lgh);
#ifdef OUTPUT_PROGRESS
		cout << "SolTransfer: Better solution discovered: " << fit << " " << sol << endl;
#endif
#ifdef DATA_COLLECTION
		// Update the number of computations
		++nb_computations;
		// Store data
		data.push_back(Triple<unsigned long, unsigned long, long double>(nb_iterations, nb_computations, fit));
#endif
		// Computation happened
		return true;
	}
	// No change happened
	return false;
}

const string corrupt(const string & str, double fault_rate) {
	string result = str;
	for (unsigned int i=0; i<str.size(); ++i)
		if (frand() < fault_rate)
			do result[i] = rand() % 256; while (result[i] == 7); // to avoid the bell when printing out
	return result;
}

bool LawsOfPhysics (SCSystem & phenomenon, const SCSystem & lop, SCSystem & system) {
	bool corrupted = false;
	string str, c_str;
	// Schema 1
	str = system.GetSchema1();
	c_str = corrupt(str, ((double)BStrToUInt(phenomenon.GetKernel(PH_K_F_RATE_IDX, INTEGER_SIZE)))/10000.);
	if (str != c_str) {
		system.ReplaceSchema1(c_str);
		corrupted = true;
	}
	// Kernel
	str = system.GetKernel();
	c_str = corrupt(str, ((double)BStrToUInt(phenomenon.GetKernel(PH_K_F_RATE_IDX, INTEGER_SIZE)))/10000.);
	if (str != c_str) {
		system.ReplaceKernel(c_str);
		corrupted = true;
	}
	// Schema 2
	str = system.GetSchema2();
	c_str = corrupt(str, ((double)BStrToUInt(phenomenon.GetKernel(PH_K_F_RATE_IDX, INTEGER_SIZE)))/10000.);
	if (str != c_str) {
		system.ReplaceSchema2(c_str);
		corrupted = true;
	}
#ifdef OUTPUT_PROGRESS
	if (corrupted)
		cout << "LawsOfPhysics: Data fault injected" << endl;
#endif
	return corrupted;
}

const bool is_str_damaged(const string & str, const string & chars) {
	bool damaged = false;
	for (unsigned int i=0; (i<str.size()) && (!damaged); ++i) {
		unsigned int j=0;
		while ((j<chars.size()) && (str[i] != chars[j])) ++j;
		if (j == chars.size()) damaged = true;
	}
	return damaged;
}

const bool fix_strings(string & str1, string & str2, const string & chars) {
	if (str1.size() != str2.size()) return false;
	bool fixed = false;
	for (unsigned int i=0; i<str1.size(); ++i) {
		if (is_str_damaged(str1.substr(i,1),chars)) {
			if (!is_str_damaged(str2.substr(i,1),chars)) {
				str1[i] = str2[i];
				fixed = true;
			}
		}
		else if (is_str_damaged(str2.substr(i,1),chars)) {
			str2[i] = str1[i];
			fixed = true;
		}
	}
	return fixed;
}

const unsigned int string_dist(const string & str1, const string & str2, const string & chars) {
	if (str1.size() != str2.size()) return false;
	unsigned int dist = 0;
	for (unsigned int i=0; i<str1.size(); ++i)
		if ((!is_str_damaged(str1.substr(i,1),chars)) &&
			(!is_str_damaged(str2.substr(i,1),chars)) &&
			(str1[i] != str2[i]))
			++dist;
	return dist;
}

bool SelfMaintenance (SCSystem & sys1, const SCSystem & maintenance, SCSystem & sys2) {
	// Check that no system is fault injection related (phenomenon)
	if ((Type(sys1) == PHENOMENON) || (Type(sys2) == PHENOMENON)) return false;
#ifdef DATA_COLLECTION
	// Update the number of iterations
	++nb_iterations;
#endif
	// Get the allowed characters
	const string chars = maintenance.GetKernel(SM_CHARS_IDX, BStrToUInt(maintenance.GetKernel(SM_NB_CHARS_IDX, INTEGER_SIZE)));
	// If one system or the other is damaged
	if (is_str_damaged(sys1.GetDefinition(),chars) || is_str_damaged(sys2.GetDefinition(),chars)) {
	 	// If two systems are similar on the non-corrupted bits then they can
		// fix each other
		if(string_dist(sys1.GetDefinition(),sys2.GetDefinition(),chars) == 0) {
			bool fixed = false;
			string str1, str2;
			// Schema 1
			str1 = sys1.GetSchema1();
			str2 = sys2.GetSchema1();
			if (fix_strings(str1, str2, chars)) {
#ifdef OUTPUT_PROGRESS
				cout << "Self-Maintenance: Schema 1 fixing" << endl;
				if (sys1.GetSchema1() != str1)
					cout << "	" << sys1.GetSchema1() << " -> " << str1 << endl;
				if (sys2.GetSchema1() != str2)
					cout << "	" << sys2.GetSchema1() << " -> " << str2 << endl;
#endif
				sys1.ReplaceSchema1(str1);
				sys2.ReplaceSchema1(str2);
				fixed = true;	
			}
			// Schema 1
			str1 = sys1.GetKernel();
			str2 = sys2.GetKernel();
			if (fix_strings(str1, str2, chars)) {
#ifdef OUTPUT_PROGRESS
				cout << "Self-Maintenance: Kernel fixing" << endl;
				if (sys1.GetKernel() != str1)
					cout << "	" << sys1.GetKernel() << " -> " << str1 << endl;
				if (sys2.GetKernel() != str2)
					cout << "	" << sys2.GetKernel() << " -> " << str2 << endl;
#endif
				sys1.ReplaceKernel(str1);
				sys2.ReplaceKernel(str2);
				fixed = true;	
			}
			// Schema 2
			str1 = sys1.GetSchema2();
			str2 = sys2.GetSchema2();
			if (fix_strings(str1, str2, chars)) {
#ifdef OUTPUT_PROGRESS
				cout << "Self-Maintenance: Schema 2 fixing" << endl;
				if (sys1.GetSchema2() != str1)
					cout << "	" << sys1.GetSchema2() << " -> " << str1 << endl;
				if (sys2.GetSchema2() != str2)
					cout << "	" << sys2.GetSchema2() << " -> " << str2 << endl;
#endif
				sys1.ReplaceSchema2(str1);
				sys2.ReplaceSchema2(str2);
				fixed = true;	
			}
#ifdef DATA_COLLECTION
			// Update the number of computations
			++nb_computations;
#endif
			// True if at least a correction happened
			return fixed;
		}
	}
	// Nothing happened
	return false;	
}

bool Dummy (SCSystem & sys1, const SCSystem & dummy, SCSystem & sys2) {
	return false;
}

////////////////////////////////////////////////////////////////////////////////
